﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using Medianamik.Core.Configuration;
using Medianamik.Core.Interfaces;
using Medianamik.Core.Sugar;

namespace Medianamik.Core.Security
{
    public static class SecurityManager
    {
        internal static SecurityProvider SecurityProvider
        {
            get { return SecurityProvider.Instance; }
        }

        internal static RoleCacheManager RoleProvider
        {
            get { return RoleCacheManager.Instance; }
        }

        public static IEnumerable<Group> AllGroups
        {
            get { return SecurityProvider.AllGroups.AsEnumerable(); }
        }

        public static IEnumerable<Permission> AllNodePermissions
        {
            get { return SecurityProvider.AllNodePermissions.AsEnumerable(); }
        }

        public static IEnumerable<InheritedPermission> AllInheritedNodePermissions
        {
            get { return SecurityProvider.AllInheritedNodePermissions.AsEnumerable(); }
        }

        public static IEnumerable<Permission> AllNodeTypePermissions
        {
            get { return SecurityProvider.AllNodeTypePermissions.AsEnumerable(); }
        }

        public static IEnumerable<Approbation> AllNodeApprobations
        {
            get { return SecurityProvider.AllNodeApprobations.AsEnumerable(); }
        }

        public static IEnumerable<InheritedPermission> AllInheritedNodeTypePermissions
        {
            get { return SecurityProvider.AllInheritedNodeTypePermissions.AsEnumerable(); }
        }

        public static IEnumerable<Guid> GetUsersInGroups(IEnumerable<string> groups)
        {
            if (groups.Count() > 0)
                return DALProviderManager.DALProvider.SecurityDALProvider.GetUsersInGroups(groups);

            return Enumerable.Empty<Guid>();
        }
        public static IEnumerable<Group> GetParentGroups(Guid? groupId)
        {
            var group = GetGroup(groupId);
            if (group != null)
            {
                var parentGroups = group.GetParentIDs(true)
                    .Select(x => AllGroups.First(g => g.ID == x));

                return parentGroups;
            }
            return new Group[] { };
        }

        public static Group GetGroup(Guid? groupId)
        {
            return AllGroups.FirstOrDefault(g => g.ID.Equals(groupId));
        }

        public static Group GetGroup(string groupName)
        {
            return AllGroups.FirstOrDefault(g => g.Name.Equals(groupName,
                StringComparison.OrdinalIgnoreCase));
        }

        public static IEnumerable<Group> GetChildGroups(Guid groupId, int depth)
        {
            return AllGroups.Where(g => g.GetParentIDs(false, depth).Contains(groupId));
        }

        internal static int GetChildGroupsCount(Guid groupId)
        {
            return AllGroups.Count(g => g.GetParentIDs(false).Contains(groupId));
        }

        public static void ReloadProviders()
        {
            SecurityProvider.Reload();
            RoleProvider.Clear();
        }

        public static void ApplyPermissionsToType(NodeType type, IEnumerable<Permission> permissions)
        {
            if (permissions.SingleOrDefault(p => !p.SecuredObjectId.Equals(type.ID)) != null)
                throw new InvalidOperationException("All permissions must belong to the updated type.");

            var tempPermissions = permissions.ToList();

            var overriddenGroups = new List<Guid>();

            foreach (var permission in permissions)
            {
                if (permission.SecurityLevel.Equals(SecurityLevels.Inherited))
                    tempPermissions.Remove(permission);
            }

            var deletedPermissions = new List<Permission>();
            var updatedPermissions = new List<Permission>();
            var newPermissions = new List<Permission>();

            foreach (var permission in type.Permissions)
            {
                var correspondingPermission = tempPermissions.
                    SingleOrDefault(p => p.SecuredObjectId.Equals(permission.SecuredObjectId) && p.GroupId.Equals(permission.GroupId));

                //Suppression
                if (correspondingPermission == null)
                {
                    deletedPermissions.Add(permission);
                }
                else
                {
                    //Modification
                    if (!correspondingPermission.SecurityLevel.Equals(permission.SecurityLevel))
                    {
                        updatedPermissions.Add(correspondingPermission);
                    }

                    tempPermissions.Remove(correspondingPermission);
                }
            }

            //Ajout
            foreach (var newPermission in tempPermissions)
            {
                newPermissions.Add(newPermission);
            }

            if (deletedPermissions.Count + updatedPermissions.Count +
                newPermissions.Count + overriddenGroups.Count > 0)
            {
                DALProviderManager.DALProvider.SecurityDALProvider.
                    TransacApplyPermissionsToType(type.ID, overriddenGroups, deletedPermissions,
                    updatedPermissions, newPermissions);

                ReloadProviders();
            }
        }

        public static IEnumerable<InheritedPermission> GetInheritedNodeTypePermissionsByTypeId(Guid typeId)
        {
            return SecurityProvider.AllInheritedNodeTypePermissions.
                Where(p => p.SecuredObjectId.Equals(typeId));
        }

        public static IEnumerable<Permission> GetNodeTypePermissionsByTypeId(Guid typeId)
        {
            return SecurityProvider.AllNodeTypePermissions.
                Where(p => p.SecuredObjectId.Equals(typeId));
        }

        public static IEnumerable<InheritedPermission> GetInheritedNodeTypePermissionsByGroupId(Guid groupId)
        {
            return SecurityProvider.AllInheritedNodeTypePermissions.
                Where(p => p.GroupId.Equals(groupId));
        }

        public static IEnumerable<Permission> GetNodeTypePermissionsByGroupId(Guid groupId)
        {
            return SecurityProvider.AllNodeTypePermissions.
                Where(p => p.GroupId.Equals(groupId));
        }

        public static void ApplyPermissionsToNode(Node node, IEnumerable<Permission> permissions)
        {
            if (permissions.SingleOrDefault(p => !p.SecuredObjectId.Equals(node.ID)) != null)
                throw new InvalidOperationException("All permissions must belong to the updated node.");

            var tempPermissions = permissions.ToList();

            var overriddenGroups = new List<Guid>();

            foreach (var permission in permissions)
            {
                if (permission.SecurityLevel.Equals(SecurityLevels.Inherited))
                    tempPermissions.Remove(permission);
            }

            var deletedPermissions = new List<Permission>();
            var updatedPermissions = new List<Permission>();
            var newPermissions = new List<Permission>();

            foreach (var permission in node.Permissions)
            {
                var correspondingPermission = tempPermissions.
                    SingleOrDefault(p => p.SecuredObjectId.Equals(permission.SecuredObjectId) && p.GroupId.Equals(permission.GroupId));

                //Suppression
                if (correspondingPermission == null)
                {
                    deletedPermissions.Add(permission);
                }
                else
                {
                    //Modification
                    if (!correspondingPermission.SecurityLevel.Equals(permission.SecurityLevel))
                    {
                        updatedPermissions.Add(correspondingPermission);
                    }

                    tempPermissions.Remove(correspondingPermission);
                }
            }

            //Ajout
            foreach (var newPermission in tempPermissions)
            {
                newPermissions.Add(newPermission);
            }

            if (deletedPermissions.Count + updatedPermissions.Count +
                newPermissions.Count + overriddenGroups.Count > 0)
            {
                DALProviderManager.DALProvider.SecurityDALProvider.
                    TransacApplyPermissionsToNode(node.ID, overriddenGroups, deletedPermissions,
                    updatedPermissions, newPermissions);

                ReloadProviders();
            }
        }

        public static void ApplyApprobationToNode(Node node, Approbation approbation)
        {        
            if(approbation == null)
                throw new ArgumentNullException("approbation");

            if (!approbation.ObjectId.Equals(node.ID))
                throw new InvalidOperationException("All permissions must belong to the updated node.");

            if (node.Approbation == null)
            {
                DALProviderManager.DALProvider.SecurityDALProvider.AddNodeApprobation(approbation);
                ReloadProviders();
            }
            else
            {
                if (node.Approbation.Xml != approbation.Xml)
                {
                    if (node.Approbation.IsOwned == false)
                    {
                        DALProviderManager.DALProvider.SecurityDALProvider.AddNodeApprobation(approbation);
                    }
                    else
                    {
                        DALProviderManager.DALProvider.SecurityDALProvider.UpdateNodeApprobation(approbation);
                    }
                    ReloadProviders();
                }
            }
        }

        public static void RemoveApprobationFromNode(Node node)
        {
            DALProviderManager.DALProvider.SecurityDALProvider.DeleteNodeApprobation(node.ID);
            ReloadProviders();
        }

        public static bool ApprobationEnabled
        {
            get { return ((MedianamikConfigSection)ConfigurationManager.GetSection(ConfigurationSectionName.MEDIANAMIK)).EnableApprobation; }
        }

        public static IEnumerable<InheritedPermission> GetInheritedNodePermissionsByNodeId(Guid id)
        {
            return SecurityProvider.AllInheritedNodePermissions.
                Where(p => p.SecuredObjectId.Equals(id));
        }

        public static IEnumerable<Permission> GetNodePermissionsByNodeId(Guid id)
        {
            return SecurityProvider.AllNodePermissions.
                Where(p => p.SecuredObjectId.Equals(id));
        }

        public static Approbation GetNodeApprobationByNode(Node node)
        {
            Approbation approbation = null;
            var parents = DataUtil.GetBottomToTopNodeIds(node.Path, true, 0);

            for (var i = 0; i < parents.Count; i++)
            {
                approbation = GetNodeApprobationByNodeId(parents.ElementAt(i));

                if(approbation != null)
                {
                    if (i > 0) approbation.IsOwned = false;
                    break;
                }
            }

            return approbation;
        }

        public static Approbation GetNodeApprobationByNodePath(string path)
        {
            Approbation approbation = null;
            var parents = DataUtil.GetBottomToTopNodeIds(path, true, 0);

            for (var i = 0; i < parents.Count; i++)
            {
                approbation = GetNodeApprobationByNodeId(parents.ElementAt(i));

                if(approbation != null)
                {
                    if (i > 0) approbation.IsOwned = false;
                    break;
                }
            }

            return approbation;
        }

        public static Approbation GetNodeApprobationByNodeId(Guid id)
        {
            return SecurityProvider.AllNodeApprobations.FirstOrDefault(p => p.ObjectId.Equals(id));
        }

        public static IEnumerable<InheritedPermission> GetInheritedNodePermissionsByGroupId(Guid groupId)
        {
            return SecurityProvider.AllInheritedNodePermissions.
                Where(p => p.GroupId.Equals(groupId));
        }

        public static IEnumerable<Permission> GetNodePermissionsByGroupId(Guid groupId)
        {
            return SecurityProvider.AllNodePermissions.
                Where(p => p.GroupId.Equals(groupId));
        }

        public static IEnumerable<Node> GetAllSecuredNodes()
        {
            return NodeManager.GetNodes(SecurityProvider.AllNodePermissions
                .Select(p => p.SecuredObjectId).Distinct().ToArray());
        }

        public static IRole[] GetRolesForPage(MedianamikPage pPage)
        {
            return DALProviderManager.DALProvider.SecurityDALProvider.GetRolesForPage(pPage.ID);
        }

        public static void AddRolesToPage(MedianamikPage pPage, string[] pRoles)
        {
            DALProviderManager.DALProvider.SecurityDALProvider.AddRolesToPage(pPage.ID, pRoles);
        }

        public static void RemoveRolesFromPage(MedianamikPage pPage, string[] pRoles)
        {
            DALProviderManager.DALProvider.SecurityDALProvider.RemoveRolesFromPage(pPage.ID, pRoles);
        }

        public static void DeleteUser(Guid userId)
        {
            DALProviderManager.DALProvider.SecurityDALProvider.DeleteUser(userId);
        }

        public static string[] GetPropertiesForGroups(string[] groupNames, bool visible)
        {
            var profileProperties = NodeTypeManager.GetNodeType(MedianamikTypes.MedianamikProfile)
                .GetAllPropertiesWithManyToManyRelations();
            var visibleProperties = DALProviderManager.DALProvider.SecurityDALProvider
                .GetVisiblePropertiesForGroups(groupNames);

            if (visibleProperties.Count() > 0)
            {
                return (visible ? profileProperties
                      .Where(p => visibleProperties.Any(propId => propId.Equals(p.ID)))
                      .Select(p => p.Name) : profileProperties
                      .Where(p => !visibleProperties.Any(propId => propId.Equals(p.ID)))
                      .Select(p => p.Name)).ToArray();
            }

            return visible ? profileProperties.Select(p => p.Name).ToArray() : new string[] {};
        }

        public static void AddPropertiesForGroup(string[] properties, string group)
        {
            var profileProperties = NodeTypeManager.GetNodeType(MedianamikTypes.MedianamikProfile)
                .GetAllPropertiesWithManyToManyRelations();

            DALProviderManager.DALProvider.SecurityDALProvider
                .AddPropertiesForGroup(profileProperties.Where(p => properties.Any(prop => 
                    prop.EqualsIgnoreCase(p.Name))) .Select(p => p.ID).ToArray(), group);
        }
    }
}
